#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <ctype.h>
#include <fcntl.h>

#define LEFT "["
#define RIGHT "]"
#define LABLE "$"
#define DELIM " "
#define LINE_SIZE 1024
#define ARGC_SIZE 32
#define EXIT_CODE 44
#define NONE -1
#define IN_RDIR 0
#define OUT_RDIR 1
#define APPEND_RDIR 2

int lastcode = 0;
int quit = 0;
extern char **environ;
char commandline[LINE_SIZE];
char *argv[ARGC_SIZE];
char pwd[LINE_SIZE];
char myenv[LINE_SIZE];
char *rdirfilename = NULL;
int rdir = NONE;

const char *_getusername()
{
    return getenv("USER");
}

const char *_gethostname()
{
    return getenv("HOSTNAME");
}

void _getpwd()
{
    getcwd(pwd, sizeof(pwd));
}

void CheckRedir(char *cmd)
{
    // ls -al -n
    // ls -al -n > / < / >> filename.txt
    char *pos = cmd;
    while (*pos)
    {
        if (*pos == '>')
        {
            if (*(pos + 1) == '>')
            {
                *pos++ = '\0';
                *pos++ = '\0';
                while (isspace(*pos))
                    pos++;
                rdirfilename = pos;
                rdir = APPEND_RDIR;
                break;
            }
            else
            {
                *pos++ = '\0';
                while (isspace(*pos))
                    pos++;
                rdirfilename = pos;
                rdir = OUT_RDIR;
                break;
            }
        }
        else if (*pos == '<')
        {
            *pos = '\0'; // ls -a -l -n < filename.txt
            pos++;
            while (isspace(*pos))
                pos++;
            rdirfilename = pos;
            rdir = IN_RDIR;
            break;
        }
        else
        {
            // do nothing
        }
        pos++;
    }
}

void Interact(char *cline, int size)
{
    _getpwd();
    printf(LEFT "%s@%s %s" RIGHT "" LABLE " ", _getusername(), _gethostname(), pwd);
    char *s = fgets(cline, size, stdin);
    assert(s);
    (void)s;
    // "abcd\n\0"
    cline[strlen(cline) - 1] = '\0';

    // ls -a -l > filename.txt
    CheckRedir(cline);
}

int SplitString(char cline[], char *_argv[])
{
    int i = 0;
    _argv[i++] = strtok(cline, DELIM);
    while (_argv[i++] = strtok(NULL, DELIM))
        ; // 故意写的 =
    return i - 1;
}

void NormalExcute(char *_argv[])
{
    pid_t id = fork();
    if (id < 0)
    {
        perror("fork");
        return;
    }
    else if (id == 0)
    {
        int fd = 0;
        if (rdir == IN_RDIR)
        {
            fd = open(rdirfilename, O_RDONLY);
            dup2(fd, 0);
        }
        else if (rdir == OUT_RDIR)
        {
            fd = open(rdirfilename, O_CREAT | O_WRONLY | O_TRUNC, 0666);
            dup2(fd, 1);
        }
        else if (rdir == APPEND_RDIR)
        {
            fd = open(rdirfilename, O_CREAT | O_WRONLY | O_APPEND, 0666);
            dup2(fd, 1);
        }
        // 让子进程执行命令
        // execvpe(_argv[0], _argv, environ);
        execvp(_argv[0], _argv);
        exit(EXIT_CODE);
    }
    else
    {
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        if (rid == id)
        {
            lastcode = WEXITSTATUS(status);
        }
    }
}

int BuildCommand(char *_argv[], int _argc)
{
    if (_argc == 2 && strcmp(_argv[0], "cd") == 0)
    {
        chdir(_argv[1]);
        _getpwd();
        // 更正环境变量 PWD 的值
        sprintf(getenv("PWD"), "%s", pwd);
        return 1;
    }
    else if (_argc == 2 && strcmp(_argv[0], "export") == 0)
    {
        strcpy(myenv, _argv[1]);
        putenv(myenv);
        return 1;
    }
    else if (_argc == 2 && strcmp(_argv[0], "echo") == 0)
    {
        if (strcmp(_argv[1], "$?") == 0) // 打印退出码
        {
            printf("%d\n", lastcode);
            lastcode = 0;
        }
        else if (*_argv[1] == '$') // 打印环境变量
        {
            char *val = getenv(_argv[1] + 1);
            if (val)
                printf("%s\n", val);
        }
        else // 直接打印 $ 后输入的内容
        {
            printf("%s\n", _argv[1]);
        }
        return 1;
    }

    // 特殊处理一下 ls
    if (strcmp(_argv[0], "ls") == 0)
    {
        _argv[_argc++] = "--color";
        _argv[_argc] = NULL;
    }
    return 0;
}

int main()
{
    while (!quit)
    {
        rdirfilename = NULL;
        rdir = NONE;
        // 1. IO交互, 获取命令行参数, < / > / >>
        Interact(commandline, sizeof(commandline));

        // commandline -> "ls -a -l -n\0" -> "ls" "-a" "-l" "-n"
        // 2. 子串分割, 解析命令行参数
        int argc = SplitString(commandline, argv);
        if (!argc)
            continue;

        // 3. 判断指令是内建命令还是普通命令
        // 内建命令, 本质就是shell内部的一个函数
        int n = BuildCommand(argv, argc);

        // 4. 执行普通命令
        if (!n)
            NormalExcute(argv);
    }
    return 0;
}